
from src.gadgets import *;
from src.common.sample import *;

from lib import RequestUtil;
from lib import Util;


 # 
 #  This class deals with the gadget rendering requests (in default config this
 #  would be /gadgets/ifr?url=<some gadget's url>). It uses the gadget server and
 #  gadget context to render the xml to a valid html file, and outputs it.
 # 
#[TODO]

class GadgetException (Exception):
    def __init__(self, value):
        self.value = value
    def __str__(self):
        return repr(self.value)

from src.common import HttpServlet;
class GadgetRenderingServlet(HttpServlet):
	def __init__(self, request, response):
		self.__context_ = None;
		self.request = request;
		self.response = response;

	 # 
	 #  Creates the gadget using the GadgetServer class and calls outputGadget
	 # 
	
	def doGet(self):

		try:
			if (self.request.get('url') != False and len(self.request.get('url')) == 0):
				raise GadgetException, "Missing required parameter: url";
				pass;

			#from lib import Logger;

			#Logger.quickLog("GadgetRenderingServlet - doGet function starts", Logger.DEBUG);
			
			#  GadgetContext builds up all the contextual variables (based on the url or post)
			#  plus instances all required classes (feature registry, fetcher, blacklist, etc)

			from src.gadgets import GadgetContext;
			
			self.__context_ = GadgetContext(self.request, self.response, 'GADGET');
			#Logger.quickLog("GadgetRenderingServlet - doGet step 1", Logger.DEBUG);
			
			#  Unfortunatly we can't do caja content filtering here, hoping we'll have a RPC service
			#  or command line caja to use for this at some point
			gadgetServer = GadgetServer();
			gadget = gadgetServer.processGadget(self.__context_);
			#Logger.quickLog("GadgetRenderingServlet - doGet step 2", Logger.DEBUG);

			self.__outputGadget_(gadget, self.__context_);
			#Logger.quickLog("GadgetRenderingServlet - doGet function ends", Logger.DEBUG);
		except:
			import sys;			
			self.__outputError_("Unexpected error:");

	 # 
	 #  If an error occured (Exception) this function echo's the Exception's message
	 #  and if the config['debug'] is True, shows the debug backtrace in a div
	 # 
	 #  @param Exception e the exception to show
	
	def __outputError_(self, e):
		from lib import Util, Config;
		Util.header(self.response, "HTTP/1.0 400 Bad Request");
		self.response.out.write("<html><body>");
		self.response.out.write("<h1>Error</h1>");
		self.response.out.write(e);
		if (Config.get('debug')):
			self.response.out.write("<p><b>Debug backtrace</b></p><div style='overflow:auto; height:300px; border:1px solid #000000'><pre>");
			self.response.out.write("</pre></div>>");
		
		self.response.out.write("</body></html>");
	

	 # 
	 #  Takes the gadget to output, and depending on its content type calls either outputHtml-
	 #  or outputUrlGadget
	 # 
	 #  @param Gadget gadget gadget to render
	 #  @param string view the view to render (only valid with a html content type)
	
	def __outputGadget_(self, gadget, context):
		from src.gadgets import HttpUtil;
		view = HttpUtil.getView(gadget, context);
		if (view.getType() == 'HTML'):
			self.__outputHtmlGadget_(gadget, context, view);
			
		elif (view.getType() == 'URL'):
			self.__outputUrlGadget_(gadget, context, view);
			

	 # 
	 #  Outputs a html content type gadget.
	 #  It creates a html page, with the javascripts from the features inline into the page, plus
	 #  calls to 'gadgets.config.init' with the container configuration (config/container.js) and
	 #  'gadgets.Prefs.setMessages_' with all the substitutions. For external javascripts it adds
	 #  a <script> tag.
	 # 
	 #  @param Gadget gadget
	 #  @param GadgetContext context
	 # 
	def __outputHtmlGadget_(self, gadget, context, view):
		externJs = "";
		externFmt = "<script src=\"%s\"></script>";
		forcedLibs = context.getForcedJsLibs();
		from lib import Config;
		#  allow the &libs=.. param to override our forced js libs configuration value
		if (forcedLibs != None and len(forcedLibs) == 0):
			forcedLibs = Config.get('focedJsLibs');
		
		self.setContentType("text/html; charset=UTF-8");
		if (context.getIgnoreCache()):
			#  no cache was requested, set non-caching-headers
			self.setNoCache(True);
		elif (self.request.get('v') != False):
			#  version was given, cache for a long long time (a year)
			self.setCacheTime(365 * 24 * 60 * 60);
		else:
			#  no version was given, cache for 5 minutes
			self.setCacheTime(5 * 60);

		from lib import Config, Util;
		#  Was a privacy policy header configured? if so set it
		if (Config.get('P3P') != ''):
			#[TODO]
			#Util.header("P3P: " + Config.get('P3P'));
			pass;
		
		if (not (view.getQuirks())):
			self.response.out.write("<!DOCTYPE HTML PUBLIC \"-# W3C# DTD HTML 4.01# EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n");
		
		self.response.out.write("<html><head><style type=\"text/css\">" + Config.get('gadget_css') + "</style></head><body>\n");
		#  Forced libs first.
		if (forcedLibs != None and len(forcedLibs) > 0):
			libs = forcedLibs.split(':');
			replacement = Config.get('default_js_prefix') + self.__getJsUrl_(libs, gadget) + "&container=" + context.getContainer();
			externFmt = externFmt + "\n";
			self.response.out.write(externFmt % (replacement));
		
		self.response.out.write("<script>\n");
		
		forcedLibsArray = None;
		if (forcedLibs != None and len(forcedLibs) > 0):
			#  if some of the feature libraries are externalized (through a browser cachable <script src="/gadgets/js/opensocial-0.7:settitle.js">
			#  type url), then we don't want to include dependencies twice, so find the complete features chain, so we can skip over those
			forcedLibsArray = forcedLibs.split(':');
			registry = self.__context_.getRegistry();
			missing = {};
			#registry.getIncludedFeatures(forcedLibsArray, forcedLibsArray, missing);
			forcedLibsArray = registry.getIncludedFeaturesResultFound(forcedLibsArray);
			missing = registry.getIncludedFeaturesResultMissing();
		for library in gadget.getJsLibraries():
			type = library.getType();
			if (type == 'URL'):
				#  TODO: This case needs to be handled more gracefully by the js
				#  servlet. We should probably inline external JS as well.
				tmpExternFmt = externFmt % (library.getContent());
				externJs = externJs + tmpExternFmt + "\n";
				#  else check if there are no forcedLibs, or if it wasn't included in their dep chain
			elif (forcedLibs == None or (forcedLibs != None and  not forcedLibsArray.has_key(library.getFeatureName()))):
				self.response.out.write(library.getContent());
			
			#  otherwise it was already included by config.forceJsLibs.
		
		self.response.out.write(self.__appendJsConfig_(context, gadget, (forcedLibs != None and len(forcedLibs) > 0)) + self.__appendMessages_(gadget) + self.__appendPreloads_(gadget, context) + "</script>");
		if (len(externJs) > 0):
			self.response.out.write(externJs);
		
		gadgetExceptions = [];
		from src.gadgets.rewrite import ContentRewriter;
		rewriter = ContentRewriter();
		if (rewriter.rewriteGadgetView(gadget, view)):
			content = gadget.getSubstitutions().substitute(view.getRewrittenContent());
		else:
			content = gadget.getSubstitutions().substitute(view.getContent());
		if (len(content) == 0):
			#  Unknown view
			gadgetExceptions.append("View: '" + context.getView() + "' invalid for gadget: " + gadget.getId().getKey());
		
		if (len(gadgetExceptions) > 0):
			gadgetError = 'Error';
			raise GadgetException, gadgetError;
		
		self.response.out.write(content + "\n<script>gadgets.util.runOnLoadHandlers();</script></body>\n</html>");
	

	 # 
	 #  Output's a URL content type gadget, it adds libs=<list:of:js:libraries>.js and user preferences
	 #  to the href url, and redirects the browser to it
	 # 
	 #  @param Gadget gadget
	 # /
	def __outputUrlGadget_(self, gadget, context, view):
		#  Preserve existing query string parameters.
		redirURI = view.getHref();
		from lib import Util;
		# [TODO]
		#queryStr = strpos(redirURI, '?') !== False ? substr(redirURI, strpos(redirURI, '?')) : '';
		queryStr  = Util.substr(redirURI, redirURI.find('?')) if redirURI.find('?') != -1 else '';

		query = queryStr;
		#  TODO: userprefs on the fragment rather than query string
		query = query + self.__getPrefsQueryString_(gadget.getUserPrefValues());
		libs = [];
		from lib import Config;
		forcedLibs = Config.get('focedJsLibs');

		if (forcedLibs == None):
			reqs = gadget.getRequires();
			
			for (key, value) in  reqs.keys():
				libs.append(key);
			
		else:
			libs = forcedLibs.split(':');


		query = query + self.__appendLibsToQuery_(libs, gadget);
		
		#  code bugs out with me because of the invalid url syntax since we dont have a URI class to fix it for us
		#  this works around that
		if (query[0] == '&'):
			query = query[1:1+len(query) - 1];
			query = ''.join(query);
			query = '?' + query;

		redirURI = redirURI  + query;
		#[TODO]
		from google.appengine.ext.webapp import RequestHandler;
		requestHandler = RequestHandler();
		requestHandler.response = self.response;
		requestHandler.request = self.request;
		requestHandler.redirect(redirURI);
		
		Util.die();

	 # 
	 #  Returns the requested libs (from getjsUrl) with the libs_param_name prepended
	 #  ie: in libs=core:caja:etc.js format
	 # 
	 #  @param string libs the libraries
	 #  @param Gadget gadget
	 #  @return string the libs=... string to append to the redirection url
	 # 
	def __appendLibsToQuery_(self, libs, gadget):
		ret = "&";
		from lib import Config;
		ret = ret + Config.get('libs_param_name');
		ret = ret + "=";
		ret = ret + self.__getJsUrl_(libs, gadget);
		return ret;

	 # 
	 #  Returns the user preferences in &up_<name>=<val> format
	 # 
	 #  @param array libs array of features this gadget requires
	 #  @param Gadget gadget
	 #  @return string the up_<name>=<val> string to use in the redirection url
	 # 
	def __getPrefsQueryString_(self, prefVals):
		ret = '';
		for (key, val) in prefVals.getPrefs():
			ret = ret + '&';
			ret = ret + Config.get('userpref_param_prefix');
			ret = ret + urlencode(key);
			ret = ret + '=';
			ret = ret + urlencode(val);
		
		return ret;



	 # 
	 #  generates the library string (core:caja:etc.js) including a checksum of all the
	 #  javascript content (?v=<md5 of js>) for cache busting
	 # 
	 #  @param string libs
	 #  @param Gadget gadget
	 #  @return string the list of libraries in core:caja:etc.js?v=checksum> format
	 # 
	def __getJsUrl_(self, libs, gadget):
		buf = '';
		from lib import Util;
		
		if (not (Util.is_array(libs)) or len(libs) == 0):
			buf = 'core';
		else:
			firstDone = False;
			for lib in libs:
				if (firstDone):
					buf = buf  + ':';
				else:
					firstDone = True;
				
				buf = buf + lib;
		
		from lib import Config;
		import md5;
		
		if(Config.get('cache_objects')):
		
			cache = self.__context_.getCache();
			md15 = cache.get(md5.new('getJsUrlMD5').hexdigest());

		else:
			md15 = False;

		if (md15 == False):
			registry = self.__context_.getRegistry();
			features = registry.getAllFeatures();
			
			#  Build a version string from the md5() checksum of all included javascript
			#  to ensure the client always has the right version
			inlineJs = '';
			
			for feature1 in features.keys():
				feature = features.get(feature1);
				
				library = feature.getFeature();
				libs = library.getLibraries(self.__context_.getRenderingContext());
				for lib in libs:
					inlineJs = inlineJs + lib.getContent();
			
			
			md15 = md5.new(inlineJs).hexdigest();
			
			if(Config.get('cache_objects')):
				cache.set(md5.new('getJsUrlMD5').hexdigest(), md15);
		
		buf = buf + ".js?v=" + md15;
		return buf;
	
	def __appendJsConfig_(self, context, gadget, hasForcedLibs):
		container = context.getContainer();
		containerConfig = context.getContainerConfig();
		# TODO some day we should parse the forcedLibs too, and include their config selectivly as well
		#  for now we just include everything if forced libs is set.
		if (hasForcedLibs):
			gadgetConfig = containerConfig.getConfig(container, 'gadgets.features');
		else:
			gadgetConfig = {};
			featureConfig = containerConfig.getConfig(container, 'gadgets.features');
			for library in gadget.getJsLibraries():
				feature = library.getFeatureName();
				if (not (gadgetConfig.has_key(feature)) and feature in featureConfig):
					gadgetConfig[feature] = featureConfig[feature];
				
		# FIXME temp fix to force the js api to use the REST interface until we finish implementing the JSON-RPC service
		if (not (gadgetConfig.has_key('opensocial-0.8'))):
			gadgetConfig['opensocial-0.8'] = {};
		
		gadgetConfig['opensocial-0.8']['impl'] = "rest";
		from lib import Util;
		return "gadgets.config.init(" + Util.json_encode(gadgetConfig) + ");\n";
	
	def __appendMessages_(self, gadget):
		msgs = '';
		if (gadget.getMessageBundle()):
			bundle = gadget.getMessageBundle();
			from lib import Util;
			msgs = Util.json_encode(bundle.getMessages());
		
		return "gadgets.Prefs.setMessages_(" + msgs + ");\n";
	

	 # 
	 #  Appends data from <Preload> elements to make them available to
	 #  gadgets.io.
	 # 
	 #  @param gadget
	 # 
	def __appendPreloads_(self, gadget, context):
		resp = '';
		from lib import Config;
		gadgetSigner = Config.get('security_token_signer');
		gadgetSigner = gadgetSigner();
		token = '';
		try:
			token = context.extractAndValidateToken(gadgetSigner);
		except:
			token = '';
			#  no token given, safe to ignore
		
		for preload in gadget.getPreloads():
			try:
				#if ((preload.getAuth() == Auth.NONE or token != None) and (len(preload.getViews()) == 0 or context.getView() in preload.getViews())):
				if ((preload.getAuth() == Auth.NONE or token != None) and (preload.getViews().len() == 0 or context.getView() in preload.getViews().values())):
					request = RemoteContentRequest(preload.getHref());
					request.createRemoteContentRequestWithUri(preload.getHref());
					request.getOptions().ownerSigned = preload.isSignOwner();
					request.getOptions().viewerSigned = preload.isSignViewer();
					
					switch = preload.getAuth().strip.upper();
					if (switch == 'NONE'):
						brc = BasicRemoteContent();
						response = brc.fetch(request, context);

					elif (switch == "SIGNED"):
						signingFetcherFactory = SigningFetcherFactory(Config.get("private_key_file"));
						fetcher = signingFetcherFactory.getSigningFetcher(BasicRemoteContentFetcher(), token);
						response = fetcher.fetch(preload.getHref(), request.getMethod());
						
					else:
						ob_end_clean();
						header("HTTP/1.0 500 Internal Server Error", True);
						Util.write("<html><body><h1>" + "500 - Internal Server Error" + "</h1></body></html>");
						Util.die();
					
					resp[preload.getHref()] = {
							'body' : response.getResponseContent(), 
							'rc' : response.getHttpCode()};
				
			except:
				raise Exception, "";
		if(len(resp)>0):
			resp = json_encode(resp);
		else:
			resp = "{}";
		return "gadgets.io.preloaded_ = " + resp + ";\n";


class ProxyServlet(HttpServlet):

	def __init__(self, request, response):
		self.request = request;
		self.response = response;
	def doGet(self):
		self.noHeaders = True;
		from src.gadgets import GadgetContext;
		context = GadgetContext(self.request, self.response, 'GADGET');
		#  those should be doable in one statement, but php seems to still evauluate the second ? and : pair,
		#  so throws an error about undefined index on post, even though it found it in get ... odd bug 
		from lib import Config, RequestUtil, Util;
		if(self.request.get('url') != False):
			url = self.request.get('url');
		else:
			url = False;
		if(self.request.get('httpMethod') != False):
			method = self.request.get('httpMethod');
		else:
			method = False;
		if (not url):
			Util.header("HTTP/1.0 400 Bad Request", True);
			self.response.out.write("<html><body><h1>400 - Missing url parameter</h1></body></html>");
			return;

		signingFetcherFactory = gadgetSigner = False;
		if (self.request.get('authz') != False and self.request.get('authz') != ''):
			gadgetSigner = Config.get('security_token_signer');
			gadgetSigner = gadgetSigner();
			signingFetcherFactory = SigningFetcherFactory(Config.get("private_key_file"));
		from src.gadgets import ProxyHandler;
		proxyHandler = ProxyHandler(self.request, self.response,context, signingFetcherFactory);
		if (self.request.get('output') != False and self.request.get('output') == 'js'):
			proxyHandler.fetchJson(url, gadgetSigner, method);
		else:
			proxyHandler.fetch(url, gadgetSigner, method);

	def doPost(self):
		self.doGet();





class JsonRpcServlet(HttpServlet):

	def doPost(self):
		try:
			# we support both a raw http post (without application/x-www-form-urlencoded headers) like java does
			# and a more php / curl safe version of a form post with 'request' as the post field that holds the request json data
			if (GLOBALS.has_key('HTTP_RAW_POST_DATA') or RequestUtil.GETORPOST.has_key('request')):
				requestParam = GLOBALS['HTTP_RAW_POST_DATA'] if GLOBALS.has_key('HTTP_RAW_POST_DATA') else RequestUtil.GETORPOST['request'];
				#if (get_magic_quotes_gpc()) {
				#	requestParam = stripslashes(requestParam);
				
				request = Util.json_decode(requestParam);

				request = GLOBALS['HTTP_RAW_POST_DATA'] if GLOBALS.has_key('HTTP_RAW_POST_DATA') else RequestUtil.GETORPOST.has_key('request');

				if (request):
					raise Exception, "Malformed json string";
				
				handler = JsonRpcHandler();
				response = handler.process(request);
				Util.write(Util.json_encode({'gadgets' : response}));
			else:
				raise Exception, "No post data set";
			
		except:
			Util.header("HTTP/1.0 500 Internal Server Error", True, 500);
			Util.write("<html><body><h1>Internal Server Error</h1><br />");
			if (Config.get('debug')):
				Util.write(e.value + "<br /><pre>");
				Util.write("</pre>");
			
			Util.write("</body></html>");
		
	

	def  doGet(self):
		from lib import Util;
		Util.header("HTTP/1.0 400 Bad Request", True, 400);
		Util.write("<html><body>");
		Util.write("<h1>Error</h1>");
		Util.write("<body></html>");


# 
# This class serves files from the app_root/javascript directory, it was created
# so that the app examples and javascript files would work out of the box with
# the php version too
# 
class FilesServlet (HttpServlet):

	# 
	# Handles the get file request, if the file exists and is in the correct
	# location it's echo'd to the browser (with a basic content type guessing
	# based on the file extention, ie .js becomes text/javascript).
	# If the file location falls outside of the app/javascript root a
	# 400 Bad Request is returned, and if the file is inside of the root
	# but doesn't exist a 404 error is returned
	# 
	def __init__(self, request, response):
		self.request = request;
		self.response = response;

	def  doGet(self):
		from lib import Config, Util;
		from lib import RequestUtil;
		requestUtilHandler = RequestUtil.getInstance();	
		# [:TODO] REQUEST_URI changed to PATH_INFO
		import os;
		#file = requestUtilHandler.SERVER["PATH_INFO"].replace(Config.get('web_prefix') + '/gadgets/files/', '');
		
		#from lib import Logger;
		#Logger.quickLog("PATH_INFO - " + os.environ["PATH_INFO"], Logger.DEBUG);

		file = os.environ["PATH_INFO"].replace(Config.get('web_prefix') + '/gadgets/files/', '');
		#Logger.quickLog("URL prefix replaced - " + file, Logger.DEBUG);
		file = Config.get('javascript_path') + file;
		#Logger.quickLog("Effective URL - " + file, Logger.DEBUG);

		# make sure that the real path name is actually in the javascript_path, so people can't abuse this to read
		# your private data from disk .. otherwise this would be a huge privacy and security issue 
		#realfile = os.path.realpath(Util.normal_path(file));
		#realjspath = os.path.realpath(Util.normal_path(Config.get('javascript_path')));
		
		realfile = Util.normal_path(file);
		realjspath = Util.normal_path(Config.get('javascript_path'));

		#Logger.quickLog("Real URL - " + realjspath, Logger.DEBUG);

		if (realfile[0:len(realjspath)] != realjspath):
			Util.header(self.response, "HTTP/1.0 400 Bad Request");
			self.response.out.write("<html><body><h1>400 - Bad Request</h1></body></html>");
			Util.die();
		
		# if the file doesn't exist or can't be read, give a 404 error
		if (not os.path.exists(file) or not os.path.isfile(file)):			
			Util.header(self.response, "HTTP/1.0 404 Not Found");
			self.response.out.write("<html><body><h1>404 - Not Found</h1></body></html>");
			Util.die();

		content = '';
		try:
			#Logger.quickLog("Read file - " + realfile, Logger.DEBUG);
			fp = open(realfile, 'r');
			content = fp.read(os.path.getsize(realfile));
			fp.close();

		except:
			Util.header(self.response, "HTTP/1.0 404 Not Found");
			self.response.out.write("<html><body><h1>404 - Not Found</h1></body></html>");
			Util.die();
		
		dot = realfile.find('.');
		
		if (dot):
			ext = file[dot + 1:].lower();
			
			if (ext == 'html' or ext == 'htm'):
				self.setContentType('text/html');
			elif (ext == 'js'):
				self.setContentType('text/javascript');
			elif (ext == 'css'):
				self.setContentType('text/css');
			elif (ext == 'xml'):
				self.setContentType('text/xml');
			elif (ext == 'png'):
				self.setContentType('image/png');
			elif (ext == 'gif'):
				self.setContentType('image/gif');
			elif (ext == 'jpg' or ext == 'jpeg'):
				self.setContentType('image/jpeg');
			
		self.setCharset('');
#		super.setLastModified(os.path.getmtime(file));[:TODO]
		self.response.out.write(content);
	


# 
# This class serves the public certificate, quick and dirty hack to make the certificate publicly accessible
# this combined with the hard coded location in SigningFetcherFactory.php : http://(host}/(prefix}/public.crt
# for the oauth pub key location makes a working whole
# 
class CertServlet (HttpServlet):
	
	# Handles the get file request, only called on url = /public.crt
	# so this function has no logic other then to output the cert
	# 
	def  doGet(self):
		from lib import config, Util;
		import os;
		file = Config.get('public_key_file');
		if (not os.path.exists(file)):
			raise Exception, "Invalid public key location (file), check config and file permissions";
		
		content = '';
		try:
			fp = open(file, 'r');
			content = fp.read(os.path.getsize(file));
			fp.close();

		except:
			raise Exception, "Invalid public key location (file), check config and file permissions";
		
		self.setLastModified(os.path.getmtime(file));
		Util.write(content);


# 
# This event handler deals with the /js/core:caja:etc.js request which content type=url gadgets can use
# to retrieve our features javascript code, or used to make the most frequently used part of the feature
# library external, and hence cachable by the browser
# 
class JsServlet (HttpServlet):
	def __init__(self, request, response):
		self.request = request;
		self.response = response;


	def  doGet(self):
		self.noHeaders = True;
		from lib import Config, Util;
		from lib import RequestUtil;
		requestUtilHandler = RequestUtil.getInstance();	

		import os;

		#if (requestUtilHandler.SERVER.has_key('HTTP_IF_MODIFIED_SINCE')):

		if (os.environ.has_key('HTTP_IF_MODIFIED_SINCE')):
			Util.header(self.response, "HTTP/1.1 304 Not Modified");
			Util.header(self.response, 'Content-Length: 0');
			#ob_end_clean();
			Util.die();
			return;

		# REQUEST_URI changed to PathINfo
		uri = os.environ["PATH_INFO"].lower();

		uri = uri[uri.rfind('/') + 1:];

		# remove any params that would confuse our parser
		if (uri.find('?') != -1):
			uri = uri[0:uri.find('?')]; 
		
		if (uri.find('.js') != -1):
			uri = uri[0:len(uri) - 3];
		
		needed = [];
		
		if (uri.find(':') != -1):
			needed = explode(':', uri);
		else:
			needed.append(uri);
		
		found = [];
		missing = [];
		registry = GadgetFeatureRegistry(Config.get('features_path'));

		

		found = registry.getIncludedFeaturesResultFound(needed);
		missing = registry.getIncludedFeaturesResultMissing();

		#if (registry.getIncludedFeatures(needed, found, missing)) 
		if (len(missing.keys()) == 0):
			containerParam = self.request.get("c") if (self.request.get("c") != None) else '';
			context = 'CONTAINER' if containerParam == '1' else 'GADGET';
			jsData = '';
			done = [];
			
			while True:
				for entry in found.keys():
					if (not entry in  done):	
						feat = registry.getEntry(entry);
						feature = feat.getFeature();
						if (isinstance(feature, JsLibraryFeatureFactory)):
							jsLib = feature;
							for lib in jsLib.getLibraries(context):
								if (lib.getType() != 'URL'):
									jsData = jsData + lib.getContent() + "\n";
								
	
						done.append(entry);

				if(len(done) == found.len()):
					break;					
			

			if (len(jsData) == 0):
				Util.header(self.response, "HTTP/1.0 404 Not Found", True);
				Util.die();
			
			self.__setCachingHeaders_();
			Util.header(self.response, "Content-Type: text/javascript");
			self.response.out.write(jsData);
		else:
			Util.header(self.response, "HTTP/1.0 404 Not Found", True);
		
		Util.die();
	

	def __setCachingHeaders_(self):
		Util.header(self.response, "Expires: Tue, 01 Jan 2030 00:00:01 GMT");
		# IE seems to need this (10 years should be enough).
		Util.header(self.response, "Cache-Control: public,max-age=315360000");
		# Firefox requires this for certain cases.
		import time;

		Util.header(self.response, "Last-Modified: " + time.strftime('%a, %d %m %Y %H:%M:%S', time.gmtime(time.time())));
	



class RestException(Exception):
	pass;

#FIXME Delete should respond with a 204 No Content to indicate success

NOT_FOUND = "notFound";
NOT_IMPLEMENTED = 'notImplemented';
UNAUTHORIZED = 'unauthorized';
FORBIDDEN = 'forbidden';
BAD_REQUEST = 'badRequest';
INTERNAL_ERROR = 'internalError';

class RestServlet (HttpServlet):

	 #Internal error code representations, these get translated into http codes in the outputError() function	

	def __init__(self, request, response):
		self.request = request;
		self.response = response;
	# The json Batch Route is used by the gadgets 
		self.__JSON_BATCH_ROUTE_ = "jsonBatch";
	# The Batch Proxy route is used the one defined in the RESTful API specification
		self.__BATCH_PROXY_ROUTE_ = "batchProxy";

	def doGet(self):
		self.doPost('GET');

	def doPut(self):
		self.doPost('PUT');

	def doDelete(self):
		self.doPost('DELETE');

	def doPost(self, method = 'POST'):
		from lib import Util;
		try:
		
			self.setNoCache(True);
			# if oauth, create a token from it's values instead of one based on _get['st']/_post['st']
			# NOTE : if no token is provided an anonymous one is created (owner = viewer = appId = modId = 0)
			# keep this in mind when creating your data services.. 
			token = self.__getSecurityToken();
			outputFormat = self.__getOutputFormat();
			if (outputFormat == 'json'):	
				from src.social.converters import OutputJsonConverter;
				self.setContentType('application/json');
				outputConverter = OutputJsonConverter(self.request, self.response);

			elif (outputFormat == 'atom'):
				self.setContentType('application/atom+xml');
				outputConverter = OutputAtomConverter();
			else:				
				self.outputError(ResponseItem(NOT_IMPLEMENTED, "Invalid output format"));
			

			if (self.isJsonBatchUrl()):				
				# custom json batch format used by the gadgets
				responses = self.__handleJsonBatchRequest(token);
				outputConverter.outputJsonBatch(responses, token);
			elif (self.isBatchProxyUrl()):			
				# spec compliant batch proxy
				# self.noHeaders = True;
				responses = self.__handleBatchProxyRequest(token);
				outputConverter.outputBatch(responses, token);
			else:
				# single rest request
				response = self.__handleRequest(token, method);
				outputConverter.outputResponse(response['response'], response['request']);

		except:
			
			Util.header(self.response, "HTTP/1.0 500 Internal Server Error");
			response = "<html><body><h1>500 Internal Server Error</h1>";
			#response = response + "Message: " + e.text + "<br />\n";
			from lib import Config;
			if (Config.get('debug')):
				response = response + "<pre>\n";
				response = response + "\n</pre>";

			response = response + "</body></html>";

			self.response.out.write(response);


	def __handleRequest(self, token, method):
		params = self.getListParams();
		requestItem = RestRequestItem();
		url = self.getUrl();
		requestType = self.getRouteFromParameter(url);
		requestFormat = self.getRequestFormat();
		requestParam = self.__getRequestParams(requestType, requestFormat);
		requestItem.createRequestItem(url, token, method, params, requestParam);
		responseItem = self.getResponseItem(requestItem);
		return {'request' : requestItem, 'response' : responseItem};


	def __handleJsonBatchRequest(self, token):
		#  we support both a raw http post (without application/x-www-form-urlencoded headers) like java does
		#  and a more php / curl safe version of a form post with 'request' as the post field that holds the request json data
		#if (isset(GLOBALS['HTTP_RAW_POST_DATA']) or isset(_POST['request'])) {
		if (self.request.get('request') != None):
			requests = self.__getRequestParams('jsonbatch');
			responses = {};
			from src.social.service import RestRequestItem
			for key in requests.keys():
				value = requests[key];
				requestItem = RestRequestItem();
				requestItem.createRequestItemWithRequest(value, token);
				responses[key] = self.__getResponseItem(requestItem);
			
			return responses;
		else:
			raise Exception, "No post data set";


	def __handleBatchProxyRequest(self, token):
		#  Is this is a multipath/mixed post? Check content type:
		from lib import RequestUtil;
		requestUtilHandler = RequestUtil.getInstance();	

		#if (isset(GLOBALS['HTTP_RAW_POST_DATA']) and strpos(_SERVER['CONTENT_TYPE'], 'multipart/mixed') !== False and strpos(_SERVER['CONTENT_TYPE'], 'boundary=') !== False) {
		if (isset(GLOBALS['HTTP_RAW_POST_DATA']) and requestUtilHandler.SERVER['CONTENT_TYPE'].find('multipart/mixed') != -1 and requestUtilHandler.SERVER['CONTENT_TYPE'].find('boundary=') != -1):
			#  Ok looks swell, see what our boundry is..
			
			pos = requestUtilHandler.SERVER['CONTENT_TYPE'].find('boundary=');

			boundry = requestUtilHandler.SERVER['CONTENT_TYPE'][pos: pos + len('boundary=')];
			#  Split up requests per boundry
			requests = GLOBALS['HTTP_RAW_POST_DATA'].split(boundry);
			responses = [];
			for request in requests:
				request = request.strip();
				#if (not empty(request)) {
				if (len(request) > 0):
					#  extractBatchRequest() does the magic parsing of the raw post data to a meaninful request array
					request = self.extractBatchRequest(request);
					requestItem = RestRequestItem();
					requestItem.createRequestItemWithRequest(request, token);
					responses.append({'request' : requestItem, 'response' : self.getResponseItem(requestItem)});

		else:
			self.outputError(ResponseItem(BAD_REQUEST, "Invalid multipart/mixed request"));

		return responses;


	def __extractBatchRequest(self, request):
		'''
		/* Multipart request is formatted like:
		 * -batch-a73hdj3dy3mm347ddjjdf
		 * Content-Type: application/http;version=1.1
		 * Content-Transfer-Encoding: binary
		 * 
		 * GET /people/@me/@friends?startPage=5&count=10&format=json
		 * Host: api.example.org
		 * If-None-Match: "837dyfkdi39df"
		 * 
		 * but we only want to have the last bit (the actual request), this filters that down first
		 */
		'''
		emptyFound = False;
		requestLines = request.split("\r\n");
		request = '';
		for line in requestLines:
			if (emptyFound):
				request = request + line + "\n";
			elif (len(line) == 0):
				emptyFound = True;
			
		
		if (not emptyFound):
			raise Exception, "Mallformed multipart structure";
		
		#  Now that we have the basic request in request, split that up again & parse it into a meaningful representation
		firstFound = False;
		emptyFound = False;
		requestLines = request.split("\n");
		request = {};
		request['headers'] = {};
		request['postData'] = '';
		for line in requestLines:
			if (not firstFound):
				firstFound = True;
				parts = line.strip().split(' ');
				if (len(parts) != 2):
					raise Exception, "Mallshaped request uri in multipart block";

				request['method'] = parts[0].upper().strip();
				#  cut it down to an actual meaningful url without the prefix/social/rest part right away 
				strlen = len(Config.get('web_prefix') + '/social/rest');
				request['url'] = parts[1].strip()[strlen:];
			elif (not emptyFound and len(line) > 0):
				#  convert the key to the PHP 'CONTENT_TYPE' style naming convention.. it's ugly but consitent
				key = line[0:line.find(':')].strip().upper().replace('-', '_');
				val = line[line.find(':') + 1:].strip();
				request['headers'][key] = val;
			elif (not emptyFound and len(line) == 0):
				emptyFound = True;
			else:
				#if (get_magic_quotes_gpc()) {
				#line = stripslashes(line);
				#}
				request['postData'] = request['postData'] + line + "\n";

		if (len(request['method']) == 0 or len(request['url']) == 0):
			raise Exception, "Mallformed multipart structure";
		
		if (len(request['postData']) == 0):
			#  don't trip the requestItem into thinking there is postData when there's not
			del(request['postData']);
		else:
			#  if there was a post data blob present, decode it into an array, the format is based on the 
			#  content type header, which is either application/json or 
			format = request['headers'].has_key('CONTENT_TYPE') and 'atom' if request['headers']['CONTENT_TYPE'].lower() == 'application/atom+xml' else 'json';
			requestType = self.getRouteFromParameter(request['url']);
			request['postData'] = self.decodeRequests(request['postData'], requestType, format);
		
		return request;
	

	def __getResponseItem(self, requestItem):
		path = self.__getRouteFromParameter(requestItem.getUrl());
		caller = False;

		from src.social.service import *;
		
		if path == 'people':
			caller = PeopleHandler;
		elif path == 'activities':
			caller = ActivitiesHandler;
		elif path == 'appdata':
			caller = AppDataHandler;
		
		elif path == 'messages':
			caller = MessagesHandler;
		
		# TODO add 'groups' and 'messages' here
		else:
			response = ResponseItem(NOT_IMPLEMENTED, path + " is not implemented");
		
		#if (class and class_exists(class, True)):
		caller = caller();
		response = caller.handleMethod(requestItem);
		
		if (response.getError() != None and not self.isJsonBatchUrl() and not self.isBatchProxyUrl()):
			#  Can't use http error codes in batch mode, instead we return the error code in the response item
			self.outputError(response);
		
		return response;


	def __decodeRequests(self, requestParam, requestType, format = 'json'):
		if (len(requestParam) == 0):
			return None;
		
		if format == 'json':
			from src.social.converters import InputJsonConverter;
			inputConverter = InputJsonConverter();
			
		elif format == 'atom':
			from src.social.converters import InputAtomConverter;
			inputConverter = InputAtomConverter();
			
		else:
			raise Exception, "Invalid or unsupported input format";
			
		
		
		if requestType == 'people':
			ret = inputConverter.convertPeople(requestParam);
			
		if requestType == 'activities':
			ret = inputConverter.convertActivities(requestParam);
			
		if requestType == 'messages':
			ret = inputConverter.convertMessages(requestParam);
			
		if requestType == 'appdata':
			ret = inputConverter.convertAppData(requestParam);
			
		if requestType == 'jsonbatch':
			#  this type is only used by the internal json batch format
			if (format != 'json'):
				raise Exception, "the json batch only supports the json input format";
			
			ret = inputConverter.convertJsonBatch(requestParam);
			
		# TODO add 'groups' and 'messages' here
		else:
			raise Exception, "Unsupported REST call";
			
		
		return ret;
	

	def __getRequestParams(self, requestType, requestFormat = 'json'):
		import cgi, sys, cStringIO 
		copyInput = cStringIO.StringIO(sys.stdin.read())
		fieldStorage = cgi.FieldStorage(copyInput)
		
		import os;
		
		post = copyInput.getvalue() if copyInput.getvalue() != None else None;
		#requestParam = GLOBALS['HTTP_RAW_POST_DATA'] if GLOBALS.has_key('HTTP_RAW_POST_DATA') else post;
		requestParam = post;
		#if (get_magic_quotes_gpc()):
		#	requestParam = stripslashes(requestParam);
		return self.__decodeRequests(requestParam, requestType, requestFormat);
	

	def __getRouteFromParameter(self, pathInfo):
		pathInfo = pathInfo[1:];
		indexOfNextPathSeparator = pathInfo.find("/");
		return pathInfo[0:indexOfNextPathSeparator] if indexOfNextPathSeparator != - 1 else pathInfo;
	

	def __outputError(self, response):
		errorMessage = response.getErrorMessage();
		
		if response.getError() == BAD_REQUEST:
			code = '400 Bad Request';
			
		elif response.getError() == UNAUTHORIZED:
			code = '401 Unauthorized';
			
		elif response.getError() == FORBIDDEN:
			code = '403 Forbidden';
			
		elif response.getError() == FORBIDDEN:
			code = '404 Not Found';
			
		elif response.getError() == NOT_IMPLEMENTED:
			code = '501 Not Implemented';
			
		
		else:
			code = '500 Internal Server Error';
				
		
		Util.header(self.response, "HTTP/1.0 code");
		self.response.out.write(code + "  - errorMessage");
		self.response.clear();
		Util.die();

	def __getSecurityToken(self):
		from lib import Config;
		token = self.request.get('st') if self.request.get('st') != None else '';
		if (len(token) == 0):
			#  no security token, continue anonymously, remeber to check
			#  for private profiles etc in your code so their not publicly
			#  accessable to anoymous usersnot Anonymous == owner = viewer = appId = modId = 0
			gadgetSigner = Config.get('security_token');
			#  create token with 0 values, no gadget url, no domain and 0 duration
			return gadgetSigner(None, 0, 0, 0, 0, '', '', 0);
		
		

		if (len(token.split(':')) != 6):
			import  urllib;
			token = urllib.quote(token.encode('base64'));
		
		gadgetSigner = Config.get('security_token_signer');
		from src.common.sample import BasicSecurityTokenDecoder;
		gadgetSigner = gadgetSigner();		
		return gadgetSigner.createToken(token);
	

	def __getOutputFormat(self):
		output = self.request.get('format') if self.request.get('format') != "" else 'json';
		#not empty(_POST['format']) ? _POST['format'] : (not empty(_GET['format']) ? _GET['format'] : 'json');
		return output.strip().lower();

	def __getListParams(self):
		#  get the rest params
		from lib import RequestUtil;
		requestUtilHandler = RequestUtil.getInstance();	
		
		from lib import Config;
		uri = requestUtilHandler.SERVER["REQUEST_URI"][len(Config.get('web_prefix') + '/social/rest') + 1: ];
		pos = uri.find('?');

		if (pos != -1):
			uri = uri[0:pos];
		
		restParams = uri.split('/');
		return restParams;
	

	def __getRequestFormat(self):
		from lib import RequestUtil;
		requestUtilHandler = RequestUtil.getInstance();	

		if (requestUtilHandler.SERVER.has_key('CONTENT_TYPE')):
			
			if requestUtilHandler.SERVER['CONTENT_TYPE'] == 'application/atom+xml':
				return 'atom';
			elif requestUtilHandler.SERVER['CONTENT_TYPE'] == 'application/json':
				return 'json';
			else:
				raise Exception, "Invalid request content type";

		#  if no Content-Type header is set, we assume json
		return 'json';
	

	def __getUrl(self):
		from lib import RequestUtil;
		requestUtilHandler = RequestUtil.getInstance();	

		return requestUtilHandler.SERVER["REQUEST_URI"][len(Config.get('web_prefix') + '/social/rest'):];

	def isJsonBatchUrl(self):
		import string;
		uri = self.request.path;
		return string.find(uri,self.__JSON_BATCH_ROUTE_) != -1;
	

	def isBatchProxyUrl(self):
		from lib import RequestUtil;
		requestUtilHandler = RequestUtil.getInstance();	

		return requestUtilHandler.SERVER["REQUEST_URI"].find(RestServlet.BATCH_PROXY_ROUTE) != -1;


